package environment

import (
    "context"
    "fmt"
    "sync"

    "git.inspur.com/x/keycloak"
    "k8s.io/api/core/v1"
    rbacv1 "k8s.io/api/rbac/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
)

const (
    DefaultSaNamespace = "iop"
    ServiceAccount     = "ServiceAccount"
)

type icpEnv struct {
    k8sClient kubernetes.Interface
}

func (c *icpEnv) GetRBACSubject(username string) rbacv1.Subject {
    subject := rbacv1.Subject{
        Kind:      ServiceAccount,
        Name:      GetRBACSubjectLabelName(username),
        Namespace: DefaultSaNamespace,
    }
    return subject
}

func (c *icpEnv) CreateUserRBACSubject(name string) error {
    return c.createServiceAccount(c.k8sClient, name)
}

func (c *icpEnv) createServiceAccount(k8sClient kubernetes.Interface, username string) error {
    logger.Debug("create service account for user %s to do rbac", username)
    name := GetRBACSubjectLabelName(username)
    sa, err := k8sClient.CoreV1().ServiceAccounts(DefaultSaNamespace).Get(context.TODO(), name, metav1.GetOptions{})
    if err != nil {
        _ = logger.Error("load user service account %s error. %v", name, err)
    } else if sa != nil && sa.Name == name {
        return nil
    }
    sa = &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: DefaultSaNamespace, Name: name}}
    _, err = k8sClient.CoreV1().ServiceAccounts(DefaultSaNamespace).Create(context.TODO(), sa, metav1.CreateOptions{})
    if err != nil {
        return fmt.Errorf("create user service account: %v", err)
    }
    return nil
}

func (c *icpEnv) ExtractToken(authToken string) string {
    if authToken == "" {
        return authToken
    }

    j, err := keycloak.Client.ValidateToken(authToken)
    if err != nil {
        _ = logger.Error("ExtractToken %s error. %v", authToken, err)
        return ""
    }
    username := j.GetUsername()
    if userTokenCache.Get(username) != "" {
        return userTokenCache.Get(username)
    }
    token, err := c.queryServiceAccountTokenByAdmin(c.k8sClient, GetRBACSubjectLabelName(username), DefaultSaNamespace)
    if token != "" {
        userTokenCache.Set(username, token)
    }
    return token

}

func (c *icpEnv) queryServiceAccountTokenByAdmin(k8sClient kubernetes.Interface, serviceAccount, namespace string) (string, error) {
    logger.Debug("QueryServiceAccountTokenByAdmin service account is %s namespace is %s", serviceAccount, namespace)
    options := metav1.GetOptions{}
    serviceAccountInfo, err := k8sClient.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), serviceAccount, options)
    if err != nil {
        _ = logger.Error("query service account as admin failed " + err.Error())
        return "", fmt.Errorf("query service account failed: %v", err)
    }
    if serviceAccountInfo != nil && len(serviceAccountInfo.Secrets) > 0 {
        secretInfo, secretError := k8sClient.CoreV1().Secrets(namespace).Get(context.TODO(), serviceAccountInfo.Secrets[0].Name, options)
        if secretError != nil {
            _ = logger.Error("query secret faile " + secretError.Error())
            return "", fmt.Errorf("query secret faile: %v", err)
        }
        return string(secretInfo.Data["token"]), nil
    } else {
        return "", nil
    }
}

var userTokenCache = UserTokenCache{Data: make(map[string]string), Lock: new(sync.Mutex)}

type UserTokenCache struct {
    Data map[string]string
    Lock *sync.Mutex
}

func (d UserTokenCache) Get(k string) string {
    d.Lock.Lock()
    defer d.Lock.Unlock()
    return d.Data[k]
}

func (d UserTokenCache) Set(k string, v string) {
    d.Lock.Lock()
    defer d.Lock.Unlock()
    d.Data[k] = v
}
